package com.atguigu.yygh.hosp.service.impl;

import com.alibaba.fastjson.JSONObject;
import com.atguigu.yygh.cmn.feign.DictFeignClient;
import com.atguigu.yygh.common.config.YyghException;
import com.atguigu.yygh.enums.DictEnum;
import com.atguigu.yygh.hosp.repository.HospitalRepository;
import com.atguigu.yygh.hosp.service.DepartmentService;
import com.atguigu.yygh.hosp.service.HospitalService;
import com.atguigu.yygh.model.hosp.BookingRule;
import com.atguigu.yygh.model.hosp.Department;
import com.atguigu.yygh.model.hosp.Hospital;
import com.atguigu.yygh.model.hosp.Schedule;
import com.atguigu.yygh.vo.hosp.BookingScheduleRuleVo;
import com.atguigu.yygh.vo.hosp.HospitalQueryVo;
import com.baomidou.mybatisplus.core.metadata.IPage;
import org.apache.tomcat.util.digester.ArrayStack;
import org.joda.time.DateTime;
import org.joda.time.DateTimeConstants;
import org.joda.time.format.DateTimeFormat;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.*;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.aggregation.Aggregation;
import org.springframework.data.mongodb.core.aggregation.AggregationResults;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;

import java.util.*;
import java.util.stream.Collectors;

/**
 * @Author zhmwi
 * @Date 2021/7/8
 * @Dersciption
 */
@Service
public class HospitalServiceImpl implements HospitalService {
    @Autowired
    private HospitalRepository hospitalRepository;

    @Autowired
    private DictFeignClient dictFeignClient;

    @Autowired
    private HospitalService hospitalService;

    @Autowired
    private MongoTemplate mongoTemplate;

    @Autowired
    private DepartmentService departmentService;
    
    //模拟医院-> 上传医院信息
    @Override
    public void save(Map<String, Object> paramMap) {
        //把Map转换为json字符串
        String hospJsonStr = JSONObject.toJSONString(paramMap);
        //将json转换为对象
        Hospital hospital = JSONObject.parseObject(hospJsonStr, Hospital.class);
        //判断医院信息是否存在
        Hospital targetHospital = hospitalRepository.getHospitalByHoscode(hospital.getHoscode());
        if (targetHospital != null){
            //若存在即修改
            //0未上线 1已上线
            hospital.setStatus(targetHospital.getStatus());
            hospital.setCreateTime(targetHospital.getCreateTime());
            hospital.setUpdateTime(new Date());
            hospital.setIsDeleted(0);
            hospital.setId(targetHospital.getId());
            hospitalRepository.save(hospital);
        }else{
            //不存在即直接保存
            hospital.setStatus(0);
            hospital.setCreateTime(new Date());
            hospital.setUpdateTime(new Date());
            hospital.setIsDeleted(0);
            hospitalRepository.save(hospital);
        }
    }
    //模拟医院-> 查询医院详情
    @Override
    public Hospital getHospitalByHoscode(String hoscode) {
        return hospitalRepository.getHospitalByHoscode(hoscode);
    }

    //本地 带条件带分页查询医院
    @Override
    public Page<Hospital> queryByPageAndCondition(Integer page, Integer limit, HospitalQueryVo hospitalQueryVo) {
        //创建排序规则
        Sort sort = Sort.by(Sort.Direction.DESC,"createTime");

        //设置分页对象 0 为第一页
        Pageable pageable = PageRequest.of(page-1, limit, sort);

        //设置查询条件
        Hospital hospital = new Hospital();
        BeanUtils.copyProperties(hospitalQueryVo,hospital);

        //创建匹配器 即如何使用查询条件
        ExampleMatcher matcher = ExampleMatcher.matching()//构建对象
                .withStringMatcher(ExampleMatcher.StringMatcher.CONTAINING)//改变默认字符串匹配方式
                .withIgnoreCase(true);//忽略大小写

        //创建匹配器实例 放入查询条件和匹配规则
        Example<Hospital> example = Example.of(hospital, matcher);

        Page<Hospital> pages = hospitalRepository.findAll(example, pageable);

        //TODO 跨模块调用查询字典 翻译编码信息 医院名 地区

        //封装医院等级数据
        pages.getContent().stream()
                .forEach(hospitalItem -> this.packHospital(hospitalItem));

        return pages;
    }

    //更新医院上线状态
    @Override
    public void updateStatus(String hosId, Integer status) {
        //根据hosId查询医院
        Hospital hospital = hospitalRepository.findById(hosId).get();
        hospital.setStatus(status);
        hospital.setUpdateTime(new Date());
        hospitalRepository.save(hospital);
    }

    //查询医院详情
    @Override
    public Map<String, Object> getHospitalInfo(String hosId) {
        //查询医院详情 并 封装医院等级
        Hospital hospital = this.packHospital(hospitalRepository.findById(hosId).get());
        HashMap<String, Object> result = new HashMap<>();
        result.put("hospital",hospital);
        //单独处理更直观
        result.put("bookingRule",hospital.getBookingRule());
        return result;
    }

    //封装医院等级数据
    public Hospital packHospital(Hospital hospital){
        //省code -> 省str
        String provinceStr = dictFeignClient.getDictionaryNameByDictCodeAndValue(hospital.getProvinceCode());
        //市
        String cityStr = dictFeignClient.getDictionaryNameByDictCodeAndValue(hospital.getCityCode());
        //区
        String districtStr = dictFeignClient.getDictionaryNameByDictCodeAndValue(hospital.getDistrictCode());
        //医院类型
                //dictFeignClient.getDictionaryNameByDictCodeAndValue(hospital.getHoscode());
        String hosTypeStr = dictFeignClient
                .getDictionaryNameByDictCodeAndValue(DictEnum.HOSTYPE.getDictCode(), hospital.getHostype());

        hospital.getParam().put("hosTypeStr",hosTypeStr);
        hospital.getParam()
                .put("fullAddress",provinceStr+"-"+cityStr+"-"+districtStr+"-"+hospital.getAddress());
        return hospital;
    }

    @Override
    public String getHospName(String hoscode) {
        Hospital hospital = hospitalRepository.getHospitalByHoscode(hoscode);
        if(null != hospital) {
            return hospital.getHosname();
        }
        return "";
    }

    //前台--> 带条件带分页查询医院
    @Override
    public Page<Hospital> getHospitalByPageAndCondition(Integer page, Integer limit, HospitalQueryVo hospitalQueryVo) {
        //设置分页对象 0为第一页
        Pageable pageable = PageRequest.of(page - 1, limit);

        //设置查询条件
        Hospital hospital = new Hospital();
        BeanUtils.copyProperties(hospitalQueryVo,hospital);

        //创建匹配器 即如何使用查询条件
        ExampleMatcher matcher = ExampleMatcher.matching()//构建对象
                .withStringMatcher(ExampleMatcher.StringMatcher.CONTAINING)//改变默认字符串匹配方式
                .withIgnoreCase(true);//忽略大小写

        //创建匹配器实例 放入查询条件和匹配规则
        Example<Hospital> example = Example.of(hospital, matcher);

        Page<Hospital> hospitalPage = hospitalRepository.findAll(example, pageable);

        //封装医院等级数据
        hospitalPage.getContent().stream()
                .forEach(hospitalItem -> this.packHospital(hospitalItem));
        return hospitalPage;
    }

    //前台--> 模糊查询医院列表
    @Override
    public List<Hospital> getHospitalByFuzzy(String hosname) {
        List<Hospital> hospitalList = hospitalRepository.getHospitalByHosnameLike(hosname);
        return hospitalList;
    }

    //前台--> 根据医院编码查询医院详情 医院基本信息 预约信息
    @Override
    public Map<String, Object> getHospitalInfoByHoscode(String hoscode) {
        HashMap<String,Object> result = new HashMap<>();
        Hospital hospital = hospitalRepository.getHospitalByHoscode(hoscode);

        hospital = this.packHospital(hospital);
        //封装医院
        result.put("hospital",hospital);
        //封装预约规则
        result.put("bookingRule",hospital.getBookingRule());
        return result;
    }

    //前台--> 获取可预约排班数据
    @Override
    public Map<String, Object> getBookingScheduleRule(Integer page, Integer limit, String hoscode, String depcode) {
        HashMap<String, Object> result = new HashMap<>();
        //获取医院挂号预约规则
        Hospital hospital = hospitalService.getHospitalByHoscode(hoscode);
        if (hospital == null){
            throw new YyghException();
        }
        BookingRule bookingRule = hospital.getBookingRule();

        //获取可预约的日期分页数据 返回值IPage<Date> 参数 page limit bookingRule
        IPage<Date> iPage = this.getListDate(page,limit,bookingRule);
        List<Date> dateList = iPage.getRecords();

        //根据查询条件 查询统计规则数据
        //设置查询条件
        Criteria criteria = Criteria.where("hoscode").is(hoscode)
                .and("depcode").is(depcode)
                .and("workDate").in(dateList);

        Aggregation agg = Aggregation.newAggregation(
                //匹配筛选条件
                Aggregation.match(criteria),
                //分组统计 分组字段 统计当前日期当前医院当前科室的号源有多少
                Aggregation.group("workDate")
                        //分组统计结果
                        .first("workDate").as("workDate")
                        //统计号源数量
                        .count().as("docCount")
                        //总号源数量
                        .sum("reservedNumber").as("reservedNumber")
                        //可预约数量
                        .sum("availableNumber").as("availableNumber")
        );
        //匹配规则,集合表,查询结果封装对象
        AggregationResults<BookingScheduleRuleVo> aggregate = mongoTemplate.aggregate(agg, Schedule.class, BookingScheduleRuleVo.class);

        List<BookingScheduleRuleVo> scheduleVoList = aggregate.getMappedResults();

        Map<Date, BookingScheduleRuleVo> scheduleVoMap = new HashMap<>();
        if (!CollectionUtils.isEmpty(scheduleVoList)){
            //key为日期 value 预约排班规则Vo
            scheduleVoMap = scheduleVoList.stream()
                    .collect(Collectors.toMap(BookingScheduleRuleVo :: getWorkDate,
                                    BookingScheduleRuleVo -> BookingScheduleRuleVo));
        }
        //合并数据 : 1.可以预约的日期dateList 2.统计排班数据
        List<BookingScheduleRuleVo> bookingScheduleRuleVoList = new ArrayList<>();
        for (int i = 0; i < dateList.size(); i++) {
            //集合数据1 -- 可以预约的日期
            Date date = dateList.get(i);

            //集合数据2 -- 预约规则
            BookingScheduleRuleVo bookingScheduleRuleVo = scheduleVoMap.get(date);
            if (bookingScheduleRuleVo == null){
                //当天没有排班
                bookingScheduleRuleVo = new BookingScheduleRuleVo();
                //当天设置 设置医生人数 = 0
                bookingScheduleRuleVo.setDocCount(0);
                //科室剩余预约数 -1表示无号
                bookingScheduleRuleVo.setAvailableNumber(-1);
            }
            bookingScheduleRuleVo.setWorkDate(date);
            bookingScheduleRuleVo.setWorkDateMd(date);
            //计算当前预约日期为周几
            String dayOfWeek = this.getDayOfWeek(new DateTime(date));
            bookingScheduleRuleVo.setDayOfWeek(dayOfWeek);

            //最后一页最后一条记录为即将预约 状态 0正常 1即将放号 -1当前已停止挂号
            if (i == dateList.size()-1 && page == iPage.getPages()){
                //长度为最后一条 分页是最后一页
                bookingScheduleRuleVo.setStatus(1);
            }else{
                bookingScheduleRuleVo.setStatus(0);
            }

            //当天预约时间过了 结束时间 不能预约
            if (i == 0 && page == 1){
                DateTime stopTime = this.getDateTime(new Date(), bookingRule.getStopTime());
                if (stopTime.isBeforeNow()){
                    //停止预约
                    bookingScheduleRuleVo.setStatus(-1);
                }
            }
            bookingScheduleRuleVoList.add(bookingScheduleRuleVo);
        }

        //最后封装,所有数据封装到Map
        //可预约日期规则数据
        result.put("bookingScheduleList", bookingScheduleRuleVoList);
        //总记录数
        result.put("total", iPage.getTotal());
        //其他基础数据
        Map<String, String> baseMap = new HashMap<>();
        //医院名称
        baseMap.put("hosname", hospitalService.getHospName(hoscode));
        //科室
        Department department = departmentService.getDepartment(hoscode, depcode);
        //大科室名称
        baseMap.put("bigname", department.getBigname());
        //科室名称
        baseMap.put("depname", department.getDepname());
        //月
        baseMap.put("workDateString", new DateTime().toString("yyyy年MM月"));
        //放号时间
        baseMap.put("releaseTime", bookingRule.getReleaseTime());
        //停号时间
        baseMap.put("stopTime", bookingRule.getStopTime());
        result.put("baseMap", baseMap);
        return result;
    }

    //获取可以预约的日期分页数据(当前日开始计算)
    private IPage getListDate(Integer page, Integer limit, BookingRule bookingRule) {
        //获取当前日期预约时间
        DateTime curTime = this.getDateTime(new Date(), bookingRule.getReleaseTime());
        //预约周期
        Integer cycle = bookingRule.getCycle();
        //判断当前时间是否已经过了预约时间限制,如果过了周期+1天
        if (curTime.isBeforeNow()){
            cycle += 1;
        }
        //封装可预约时间 最后一天显示即将放号倒计时
        ArrayList<Date> dateList = new ArrayList<>();
        for (Integer i = 0; i < cycle; i++) {
            //计算当前预约日期
            DateTime curDateTime = new DateTime().plusDays(i);
            String dateStr = curDateTime.toString("yyyy-MM-dd");
            dateList.add(new DateTime(dateStr).toDate());
        }
        //日期分页,由于预约时间不一样 页面一排最多显示7天数据,多了就要分页显示
        List<Date> pageDateList = new ArrayList<>();
        int start = (page-1) * limit;
        int end = (page - 1) * limit + limit;
        if (end > dateList.size()){
            end = dateList.size();
        }
        for (int i = start ; i < end ; i++){
            pageDateList.add(dateList.get(i));
        }
        IPage iPage = new com.baomidou.mybatisplus.extension.plugins.pagination.Page<>(page, 7, dateList.size());
        iPage.setRecords(pageDateList);
        return iPage;
    }
    /**
     * 将Date日期（yyyy-MM-dd HH:mm）转换为DateTime
     */
    private DateTime getDateTime(Date date, String timeString) {
        String dateTimeString = new DateTime(date).toString("yyyy-MM-dd") + " "+ timeString;
        DateTime dateTime = DateTimeFormat.forPattern("yyyy-MM-dd HH:mm").parseDateTime(dateTimeString);
        return dateTime;
    }
    /**
     * 根据日期获取周几数据
     * @param dateTime
     * @return
     */
    private String getDayOfWeek(DateTime dateTime) {
        String dayOfWeek = "";
        switch (dateTime.getDayOfWeek()) {
            case DateTimeConstants.SUNDAY:
                dayOfWeek = "周日";
                break;
            case DateTimeConstants.MONDAY:
                dayOfWeek = "周一";
                break;
            case DateTimeConstants.TUESDAY:
                dayOfWeek = "周二";
                break;
            case DateTimeConstants.WEDNESDAY:
                dayOfWeek = "周三";
                break;
            case DateTimeConstants.THURSDAY:
                dayOfWeek = "周四";
                break;
            case DateTimeConstants.FRIDAY:
                dayOfWeek = "周五";
                break;
            case DateTimeConstants.SATURDAY:
                dayOfWeek = "周六";
            default:
                break;
        }
        return dayOfWeek;
    }
}
